/**
 * 默认报告样式
 *
 * Created by nongzhizhong on 2024/12/26.
 */
const fs = require('fs-extra');
const path = require('path');
const compressing = require('compressing');
const mongoose = require('mongoose');
const defects = require('../../../defects');
const BaseReporter = require('./../base-report');
const utility = require('./../../../utility');
const gitClient = require('../../../git/git-client');
const rulesModels = require('../../../rules');
const User = require('../../../mongo/user');
const Function = require('../../../mongo/function');
const File = require('../../../mongo/file');
const FunctionRelation = require('../../../mongo/function-relation');
const ProgressIndicator = require('../../../progress-indicator');
// 按源码文件拆分
const split = 150;
class DefaultReporter extends BaseReporter {
  constructor(versionId, locale, version = null, project = null, creator = null, outputDir = null, format, filters = {}, reportCategory = null, baseDir = null) {//eslint-disable-line
    super(versionId, locale, version, project, creator, outputDir, baseDir || __dirname, format);
    this.filters = filters;
  }

  /**
   * 报告生成
   */
  async generate(progressIndicator = new ProgressIndicator()) {
    const result = {
      cachePath: this.outputDir,
    };
    const [filesProgressIndicator, statisticsProgressIndictor, zipFilesProgressIndictor] = progressIndicator.split([0.5, 0.4, 0.1]);
    const msData = await this.getSoftwareMetricsStatistics(statisticsProgressIndictor);
    await this.handleFileRootPath();
    const { versionPath } = await this.spliteByProjectVersion((versionDir) => fs.ensureDir(versionDir));
    const ruleSetIds = (await rulesModels.filterRuleSetByLicense()).map((id) => id.toString());
    const defectDatas = await defects.getDefects(this.versionId, {
      levels: this.filters.levels,
      ruleSets: this.filters.ruleSets ? this.filters.ruleSets.filter((id) => ruleSetIds.includes(id)) : ruleSetIds,
      status: this.filters.defectStatus,
    });
    const defectsData = await this.handleFilesRuleDefects(defectDatas, filesProgressIndicator);
    const sourceFiles = defectsData.files.map((file) => ({ index: file.index, filePath: file.filePath }));
    const splitFiles = utility.arrayChunk(defectsData.files, split);
    const data = {
      projectName: this.project.projectName,
      versionName: this.version.versionName,
      ...msData,
      level1Count: defectsData.level1Count,
      level2Count: defectsData.level2Count,
      level3Count: defectsData.level3Count,
      level1: defectsData.level1,
      sourceFiles,
    };
    await utility.arrayChunkOperation(splitFiles, 1, (files, index) => this.generateReport('file-template', { ...data, files },
      path.join(versionPath, index > 0 ? `report-${index}` : 'report'), { generateToc: true }));
    const fileName = `${this.project.projectName}-${this.version.versionName}-${this.getReportName()}-${this.format}.zip`;
    result.reportPath = path.join(this.outputDir, fileName);
    const reportsDir = path.join(this.outputDir, this.project.projectName);
    await compressing.zip.compressDir(reportsDir, result.reportPath);
    zipFilesProgressIndictor.finish();
    return result;
  }

  // 获取函数调用最大嵌套深度
  async getInvokeFunctionMaxDepth() {
    let maxDepth = 1;
    const ignoreNodes = new Set();
    const relationsMap = new Map();
    const relations = await FunctionRelation.find({ versionId: mongoose.Types.ObjectId(this.version._id) }).lean();
    relations.forEach((relation) => {
      if (!relation.toFuncId) {
        return;
      }
      if (!relationsMap.has(relation.fromFuncId.toString())) {
        relationsMap.set(relation.fromFuncId.toString(), []);
      }
      relationsMap.get(relation.fromFuncId.toString()).push(relation.toFuncId.toString());
    });
    function resc(startNodeId, depth, parents) {
      if (!relationsMap.has(startNodeId)) {
        return;
      }
      depth += 1;
      if (depth > maxDepth) {
        maxDepth = depth;
      }
      relationsMap.get(startNodeId).forEach((children) => {
        if (parents.has(children)) {
          return;
        }
        ignoreNodes.add(children);
        resc(children, depth, new Set([...parents, children]));
      });
    }
    relations.forEach((relation) => {
      if (ignoreNodes.has(relation.fromFuncId.toString())) {
        return;
      }
      resc(relation.fromFuncId.toString(), 1, new Set());
    });
    return maxDepth;
  }

  // 获取软件度量统计信息
  async getSoftwareMetricsStatistics(statisticsProgressIndictor) {
    const msData = {
      localVariableSizeMax: 0,
      cycleComplexityMax: 0,
      fanOutMax: 0,
      fanInMax: 0,
      funcLoopDepthMax: 0,
      funcNestedMax: 0,
      commentCoverage: 0,
      gotoCount: 0,
      sourceLines: 0,
      commentLines: 0,
    };
    const denominator = 4;
    const sourceExts = Object.values(this.version.languages)
      .reduce((arr, item) => arr.concat(item.suffixes), []);
    const headerExts = this.version.headers.suffixes;
    const files = await File.find({ versionId: this.version._id, extname: { $in: [...sourceExts, ...headerExts] } }, ['numberOfLines', 'commentLines']);
    files.forEach((file) => {
      msData.sourceLines += file.numberOfLines;
      msData.commentLines += file.commentLines;
    });
    msData.commentCoverage = `${((msData.commentLines / msData.sourceLines) * 100).toFixed(2)}%`;
    statisticsProgressIndictor.advance(1 / denominator);
    const funcs = await Function.find({ versionId: this.version._id }, ['fanIn', 'fanOut', 'cycleComplexity', 'goto', 'loopDepthMax', 'localVariableSizeMax']);
    funcs.forEach((func) => {
      if (func.fanIn) {
        msData.fanInMax = Math.max(msData.fanInMax, func.fanIn);
      }
      if (func.fanOut) {
        msData.fanOutMax = Math.max(msData.fanOutMax, func.fanOut);
      }
      if (func.cycleComplexity) {
        msData.cycleComplexityMax = Math.max(msData.cycleComplexityMax, func.cycleComplexity);
      }
      if (func.goto) {
        msData.gotoCount += func.goto;
      }
      if (func.loopDepthMax) {
        msData.funcLoopDepthMax = Math.max(msData.funcLoopDepthMax, func.loopDepthMax);
      }
      if (func.localVariableSizeMax) {
        msData.localVariableSizeMax = Math.max(msData.localVariableSizeMax, func.localVariableSizeMax);
      }
    });
    statisticsProgressIndictor.advance(1 / denominator);
    msData.funcNestedMax = await this.getInvokeFunctionMaxDepth();
    statisticsProgressIndictor.finish();
    return msData;
  }

  // 处理规则集统计数据
  async handleFilesRuleDefects(defectDatas = [], filesProgressIndicator) {
    let resObj = {
      level1Count: 0,
      level2Count: 0,
      level3Count: 0,
    };
    const levelsMap = new Map();
    const filesDefectsMap = new Map();
    const len = 100;
    const defectDatasLen = defectDatas.length;
    await utility.arrayChunkOperation(defectDatas, len, ({
      ruleSetName, ruleId, ruleName, level, fileId, filePath, fileName,
    }) => {
      ruleId = ruleId.toString();
      if (!levelsMap.has(ruleId)) {
        levelsMap.set(ruleId, {
          ruleSetName, ruleName, count: 0, level,
        });
      }
      resObj[`level${level}Count`] += 1;
      levelsMap.get(ruleId).count += 1;
      fileId = fileId.toString();
      if (!filesDefectsMap.has(fileId)) {
        filesDefectsMap.set(fileId, { filePath, fileName, level1: new Map() });
      }
      if (level === rulesModels.ruleLevel.mandatory) {
        if (!filesDefectsMap.get(fileId).level1.has(ruleId)) {
          filesDefectsMap.get(fileId).level1.set(ruleId, { ruleSetName, ruleName, count: 0 });
        }
        filesDefectsMap.get(fileId).level1.get(ruleId).count += 1;
      }
      filesProgressIndicator.advance(len / defectDatasLen);
    });
    const level1s = [...levelsMap.values()].filter((obj) => obj.level === rulesModels.ruleLevel.mandatory);
    resObj = {
      level1Count: resObj.level1Count,
      level2Count: resObj.level2Count,
      level3Count: resObj.level3Count,
      level1: level1s.map((level, index) => ({ ...level, index: index + 1 })),
      files: [...filesDefectsMap.values()].map(({ fileName, filePath, level1 }, index) => {
        const obj = {
          index: index + 1,
          fileName,
          filePath,
          level1: [],
        };
        obj.level1 = [...level1.values()].map((defect, index) => ({ index: index + 1, ...defect }));
        return obj;
      }),
    };
    return resObj;
  }

  async handleFileRootPath() {
    const rootPath = gitClient.getLocalRepoPath({ _id: this.version.creatorId }, this.project);
    const user = await User.findOne({ _id: mongoose.Types.ObjectId(this.version.creatorId) });
    await gitClient.checkoutBranch(user, this.project, this.version, this.version.currentCommitId.toString());
    this.rootPath = rootPath;
  }
}
module.exports = DefaultReporter;
